<?php
/**
 * Created by sublime 3.
 * Auth: Inhere
 * Date: 15-1-20
 * Time: 10:35
 * Uesd: 主要功能是 hi
 * AbstractApplication.php
 */

namespace ulue\core\application;

use ulue\core\abstracts\AbstractCore;
use ulue\core\utils\Configuration;
use ulue\core\ioc\Container;
use ulue\core\controller\AbstractController;
use ulue\core\module\AbstractModule;
use ulue\core\request\InterfaceRequest;
use ulue\core\request\Request;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerAwareInterface;

abstract class AbstractApplication extends AbstractCore implements LoggerAwareInterface
{
    /**
     * @var Configuration
     */
    protected $params;

    /**
     * @var LoggerInterface
     */
    protected $logger;


////////////////////////////////// app property define //////////////////////////////////

    protected  $debug    = false;


    // 应用目录(/src) 命名空间
    // public  $namespace = 'src';
    public  $defaultModule     = 'main';
    public  $defaultController = 'default';
    public  $defaultAction     = 'index';
    // public  $autoload      = true;

    public $basePath = SRC_PATH;

    public $id       = 'APP';
    public $layout   = 'main';
    public $charset  = 'UTF-8';
    public $language = 'en';
    public $name     = 'A Application';
    public $theme;
    public $timeZone = 'PRC';
    public $suffix   = [
        'module'       => 'Module'          // 默认模块class名称后缀
        ,'controller'  => 'Controller'      // 默认控制器类名称后缀
        ,'actionClass' => 'Action'          // 默认控制器动作绑定类名称后缀
        ,'action'      => 'Action'          // 默认控制器动作方法名称后缀
        ,'model'       => ''                // 默认模型后缀 suffix--后缀 Model
        // ,'view'        => "html"            // 视图(模板)文件后缀
        // ,'layout'      => "php"            // 布局(模板)文件后缀
    ];

    public $ignoreFileCase    = false;
    // public $actionBindToClass = [];
    public $events            = [];

    /**
     * 原始的模块配置信息
     * @var array
     */
    protected $rawModules   = [];

    /**
     * 整理后的模块配置信息
     * @var array
     */
    protected $modules       = [];

    protected $moduleNames       = [];

    /**
     * 别名引入
     * @var array
     */
    public $aliases           = [];

    /**
     * 命名空间引入
     * @var array
     */
    public $namespaceImport   = [];

    /**
     * class 引入
     * @var array
     */
    public $classImport       = [];

////////////////////////////////// ./app property define //////////////////////////////////

    /**
     * @var Container
     */
    protected $container;

    /**
     * a object extends Module
     * @var \ulue\core\module\AbstractModule
     */
    protected $module;

    /**
     * a object extends Controller
     * @var \ulue\core\controller\AbstractController
     */
    protected $controller;

    /**
     * 正式环境约定名称
     * @var string
     */
    protected $prodEnvName  = 'prod';

    protected $loadedModules = [];

    /**
     * @event Event an event raised before the application starts to handle a request.
     */
    const EVENT_BEFORE_REQUEST = 'beforeRequest';

    /**
     * @event Event an event raised after the application successfully handles a request (before the response is sent out).
     */
    const EVENT_AFTER_REQUEST = 'afterRequest';

    const EVENT_BEFORE_EXECUTE = 'beforeExecute';

    const EVENT_AFTER_EXECUTE = 'afterExecute';


    const EVENT_AFTER_RESPONSE = 'beforeRespond';

    const EVENT_BEFORE_RESPONSE = 'afterRespond';


    // protected $appState = 0;

    // const APP_BEGIN      = 0;
    // const APP_EXECUTE    = 1;
    // const APP_ROUTE      = 2;
    // const APP_RESPONSE   = 3;

    public function __construct(array $config = [])
    {
        $this->params = new Configuration();

        if (isset($config['params'])) {
            $this->params->loadArray($config['params']);

            unset($config['params']);
        }

        \Ulue::loadAttrs($this, $config);

        $this->init();
    }

    protected function init()
    {
        $this->setTimeZone($this->timeZone ? : 'PRC');

        // 别名、命名空间、类引入
        $this->loadBaseAliases()
             ->aliasesImport((array) $this->aliases)
             ->namespaceImport($this->namespaceImport)
             ->classImport($this->classImport);
    }

//////////////////////////// prepare init /////////////////////////////

    // 基础别名
    protected function loadBaseAliases()
    {
        $pathManager = \Ioc::get('pathManager');

        // 路径别名设置
        $aliases       = [
            '@project'       => $pathManager->basePath(),
                '@comm'           => $pathManager->getPath('comm'),        // COMM_PATH,
                '@src'           => $pathManager->getPath('src'),        // SRC_PATH,
                '@run'           => $pathManager->getPath('run'),        // RUN_PATH,
                '@web'           => $pathManager->getPath('web'),        // WEB_PATH,
                    '@static'        => $pathManager->getPath('static'), //STATIC_PATH,
                '@vendor'        => $pathManager->getPath('vendor'),     //VENDOR_PATH,
                    '@system'        => SYS_PATH,
        ];

        $pathManager->loadAliases($aliases)->lockAliases(array_keys($aliases));

        return $this;
    }

    // 别名引入
    public function aliasesImport($aliases = [])
    {
        foreach ($aliases as $alias=>$path) {
            Ulue::setAlias($alias, $path);
        }

        return $this;
    }

    /**
     * 在 配置文件的 'namespace_import'配置 自动引入需要的命名空间
     * 检查主应用目录命名空间配置，并设置主应用目录的命名空间的映射关系
     * @return $this self
     */
    public function namespaceImport($importNamespace = [])
    {
        foreach ((array)$importNamespace as $np => $path) {
            $this->setNamespace($np, $path);
        }

        return $this;
    }

    /**
     * 引入自定义 类文件映射 @see config.php -> 'auto_import'
     * 在 配置文件的 'class_import'配置 自动引入其它目录下的某个文件|所有文件
     * @return $this
     * @throws \ulue\loader\LoadException
     */
    public function classImport($importFile = [])
    {
        $loader = \Ioc::get('loader');

        foreach( (array) $importFile as $name=>$file) {
            if (is_string($name) && is_string($file)) {
                $loader->addClass( $name, $file );
            }
        }

        unset($loader,$importFile);
    }

//////////////////////////// execute app /////////////////////////////


    protected function beforeExecute()
    {}

    abstract protected function beginExecute();

    public function execute()
    {
        $this->beforeExecute();

        // @event onBeforeExecute

        $this->beginExecute();

        // @event onAfterExecute

        $this->afterExecute();

    }

    protected function afterExecute()
    {}


//////////////////////////////////// config params ////////////////////////////////////

    /**
     * 配置数据中的 'params' 项，单独独立出来
     * get : Ulue::$app->params['title']
     * set : Ulue::$app->params['title'] = 'first app'
     * @return Configuration
     */
    public function getParams()
    {
        return $this->params;
    }

    /**
     * @param Configuration $params
     * @return self
     */
    public function setParams(Configuration $params)
    {
        $this->params = $config;

        return $this;
    }

//////////////////////////////////////// attributes ////////////////////////////////////////

    public function getTimeZone()
    {
        return date_default_timezone_get();
    }

    public function setTimeZone($value)
    {
        date_default_timezone_set($value);
    }

    /**
     * @param LoggerInterface $logger
     * @return self
     */
    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;

        return $this;
    }

    public function getLogger()
    {
        return $this->logger;
    }

    /**
     * @return AbstractModule
     */
    public function getModule()
    {
        return $this->module;
    }

    /**
     * @param AbstractModule $module
     */
    public function setModule(AbstractModule $module)
    {
        $this->module = $module;
    }

    /**
     * @param \ulue\core\controller\AbstractController $ctrl
     */
    public function setController(AbstractController $ctrl)
    {
        $this->controller = $ctrl;

        return $this;
    }

    /**
     * @return \ulue\core\controller\AbstractController
     */
    public function getController()
    {
        return $this->controller;
    }

    public function quit($msg=null)
    {
        exit($msg);
    }

    /**
     * setProdEnvName
     * @param string $value
     * @return self
     */
    public function setProdEnvName($value)
    {
        $this->prodEnvName = $value;

        return $this;
    }

    public function getProdEnvName()
    {
        return $this->prodEnvName;
    }

    /**
     * @param $path
     * @return $this
     */
    public function setBasePath($path)
    {
        if ( is_dir($path) ) {
            $this->basePath = $path;
        }

        return $this;
    }

    /**
     * @return string
     */
    public function getBasePath()
    {
        return $this->basePath;
    }

    /**
     * @return boolean
     */
    public function getDebug()
    {
        return (bool) $this->debug;
    }

    /**
     * @param boolean $value
     */
    public function setDebug($value)
    {
        $this->debug = (bool)$value;
    }

    public function getSuffix($name)
    {
        return isset($this->suffix[$name]) ? $this->suffix[$name] : '';
    }

////////////////////////////////////// Services Container //////////////////////////////////////

    /**
     * 设置系统容器
     * @param Container $container
     * @return $this
     */
    public function setContainer(Container $container)
    {
        $this->container = $container;

        return $this;
    }

    /**
     * 获取系统使用的容器
     * @return Container
     */
    public function getContainer()
    {
        return $this->container;
    }
    // public function getDI()
    // {
    //     return $this->container;
    // }
    public function getDi()
    {
        return $this->container;
    }
    // public function getIOC()
    // {
    //     return $this->container;
    // }
    public function getIoc()
    {
        return $this->container;
    }

////////////////////////////////////// Application Services //////////////////////////////////////

    /**
     * 获取应用服务
     * @see self::_services
     * @param $id
     * @return object|null
     */
    public function getService($id, array $params= [], $bindType=Container::OVERLOAD_PARAM)
    {
        if ( ! $this->container->exists($id) ) {
            return null;
        }

        return $this->container->get($id);
    }

    /**
     * 获取应用服务
     * @param string $key
     * @param null $default
     * @return mixed
     */
    public function get($id)
    {
        return $this->getService($id);
    }

    public function hasService($id)
    {
        return Ulue::$di->exists($id);
    }

    public function __get($name)
    {
        if ( $service = $this->getService($name) ) {
            return $service;
        }

        return parent::__get($name);
    }

    /**
     * @return Request|InterfaceRequest
     */
    public function getRequest()
    {
        return $this->get('request');
    }


////////////////////////////////////// handle modules config //////////////////////////////////////

    /**
     * 整理 模块信息
     * @param  array $modulesConfig 启用并设置模块的配置数组 (at config.php)
     * 'modules' => [
     *       'admin', 'home', // 启用模块
     *       'builder'  => [
     *           // 若配置模块入口类，则将根据此配置来查找模块，即 模块不一定必须在 src/ 目录下
     *           // 注意：自定义特殊模块目录时，请先配置好命名空间对应路径引入 -- 设置'path' ，也可在 'namespaceImport' 项设置
     *           'class'                => 'builder\BuilderModule',
     *           'path'                 => '@system/bags/builder',
     *
     *           // 属性设置
     *           'accessPwd'            => '12345',                  #独立访问密码
     *           'ipFilters'            => ['127.0.0.1','::1'],      #允许访问的ip
     *           'protected'            => 'admin,shop,book',        #受保护模块，删除模块需要密码
     *           'delete_pwd'           => 'ok123',                  #删除模块密码
     *
     *            // 子模块
     *           'modules' => [
     *                  'submodule1',
     *                  'submodule2' => ['aa'=>343434],
     *                  ]
     *           ],
     *   ]
     * @return void
     */
    public function setModules(array $modulesConfig)
    {
        $loaded    = [];
        $this->rawModules = $modulesConfig;


        $this->modules = $this->_handleModules($modulesConfig);
        $defaultModule = $this->defaultModule;

        // 默认模块，不用在配置中显式定义
        if ($defaultModule && !isset($loaded[$defaultModule]) ) {
            $this->modules[$defaultModule] = [];
            $this->moduleNames[] = $defaultModule;
        }

        unset($modulesConfig, $loaded);

        return $this;
    }

    private function _handleModules(array $modulesConfig)
    {
        foreach ($modulesConfig as $key => $value) {
            // 仅一个名称
            if (is_string($value)) {
                $this->moduleNames[] = $name = trim($value);
                $loaded[$name] = [];
            } elseif (is_array($value)) {
                $this->moduleNames[] = $name = trim($key);
                $loaded[$name] = $value;

                if ( !empty($value['modules']) ) {
                    $subMoudles    = $value['modules'];
                    $loaded[$name]['modules'] = $this->_handleModules($subMoudles);
                }
            }
        }

        return $loaded;
    }

    /**
     * Gets the 整理后的模块配置信息.
     *
     * @return array
     */
    public function getModules()
    {
        return $this->modules;
    }

    /**
     * Gets the 所有模块名称.
     *
     * @return array
     */
    public function getModuleNames()
    {
        return $this->moduleNames;
    }

    // /**
    //  * Gets the 整理后的模块配置信息.
    //  *
    //  * @return array
    //  */
    // public function getModuleConfig($name)
    // {
    //     return $this->modules;
    // }

    /**
     * Gets the 原始的模块配置信息.
     *
     * @return array
     */
    public function getRawModules()
    {
        return $this->rawModules;
    }


    public function isModule($moduleName)
    {
        return $this->isModule($moduleName);
    }

    /**
     * 仅仅是判断是否存在模块名
     * @param  string  $name
     * @return boolean
     */
    public function hasModule($name)
    {
        return in_array($name, $this->moduleNames);
    }

    public function findModule($name)
    {
        $name = trim($name, '/ ');

        if ( strpos($name, '/') ) {
            $names = array_values(array_filter(explode('/', $name)));

            if ( isset($names[1]) ) {
                $parent = array_shift($names);

                return $this->findSubmodule( implode('/', $names), $parent, $this->modules);
            }
        }

        if (isset($this->modules[$name])) {
            return $this->modules[$name];
        }

        return null;
    }

    /**
     * 子模块查找
     * @param  string $name   要查找的子模块名
     * @param  string $parent 父级模块名
     * @param  mixed  $modules 模块配置
     * @return mixed
     */
    protected function findSubmodule($name, $parent, $modules)
    {
        $name = trim($name, '/ ');

        if ( !is_array($modules) || !isset($modules[$parent]) ) {
            return null;
        }

        // 当前父模块配置数据 是否有子模块配置
        if ( !isset($modules[$parent]['modules'])) {
            return null;
        }

        // 切换载入当前父模块的子模块配置数据
        $modules = $modules[$parent]['modules'];

        // 有无子模块
        if (isset($modules[$name])) {
            return $modules[$name];
        }

        // 再下一层查找
        if (strpos($name, '/')) {
            $names = array_values(array_filter(explode('/', $name)));

            if ( isset($names[1]) ) {
                $parent = array_shift($names);

                return $this->findSubmodule( implode('/', $names), $parent, $modules);
            }
        }

        return null;
    }
}